#include "xymodem.h"
#include "crc.h"
#include <QDebug>

Xymodem::Xymodem() : m_com(nullptr), m_file(nullptr), m_type(xmodem)
{
}

Xymodem::Xymodem(ICom* com) : m_com(com), m_file(nullptr), m_type(xmodem)
{
}

Xymodem::~Xymodem()
{
	delete m_file;
}

void Xymodem::SetComInterface(ICom* com)
{
	m_com = com;
}

void Xymodem::SetType(const QString type)
{
	if (type == "xmodem")
	{
		this->m_type = xmodem;
	}
	else if (type == "ymodem")
	{
		this->m_type = ymodem;
	}
	else if (type == "zmodem")
	{
		this->m_type = zmodem;
	}
	else
	{
		this->m_type = xmodem;
	}
}

void Xymodem::SetFile(const QString& file_path)
{
	m_file = new XymodemFile(file_path);
	if (m_file->size() <= 0)
	{
		delete m_file;
		m_file = nullptr;
	}
}

int Xymodem::start() const
{
	ComMessage msg;
	modem_t reply;
	int tryCount = 0;
	bool leave = false;
	if (m_com == nullptr)
	{
		return -1;
	}

	if (!m_com->IsConnected())
	{
		if (!m_com->Connect())
		{
			return -2;
		}
	}

	// prepare the file to be translated
	if (m_file == nullptr)
	{
		qDebug() << "file not available!";
		return -4;
	}

	// wait for the C
	tryCount = 5;
	do {
		if (tryCount-- == 0)
		{
			// 重试次数达到上限，本次发送失败，退出
			return -3;
		}
		if (CHC == get(msg))
		{
			leave = true;
		}
	} while (!leave);
	leave = false;

	// send the header packet
	QByteArray header = make_head_packet();

	tryCount = 3;
	do
	{
		if (tryCount-- == 0)
		{
			// 重试次数达到上限
			return -7;
		}
		reply = post(msg, header);
		switch (reply)
		{
		case ACK:
			reply = get(msg);
			if (CHC == reply)
			{
				leave = true;
			}
			break;
		case TIMEOUT:
			qDebug() << "sent header but no response!";
			return -5;

		case NAK:
			qDebug() << "sent header but got error in receiving";
			return -6;
		default:;
		}
	} while (!leave);

	// send the body packet
	QByteArray body;
	int block = 0, retryCount = 3;
	do
	{
		body = make_body_packet(block);
		if (body.size() == 0)
		{
			break;
		}
		reply = post(msg, body);
		switch (reply)
		{
		case ACK:
			block++;
			break;;
		case CHC:
			break;;
		case NAK:
		{
			if (retryCount-- > 0)
			{
				break;
			}
			put(msg, make_cancel_packet());
			return -8;
		}
		case TIMEOUT:
		{
			if (retryCount-- > 0)
			{
				break;
			}
			put(msg, make_cancel_packet());
			return -9;
		}
		default:
			put(msg, make_cancel_packet());
			return -10;
		}
	} while (true);

	// send EOT
	do
	{
		reply = post(msg, make_eot_packet());
		if (reply == ACK)
		{
			break;
		}
	} while (true);

	while (CHC != get(msg)) {}

	reply = post(msg, make_null_paclet());
	if (ACK == reply)
	{
		return 0;
	}
	else
	{
		put(msg, make_cancel_packet());
		return -11;
	}
}

Xymodem::modem_t Xymodem::get(ComMessage &msg) const
{
	if (0 == m_com->GetPacket(msg))
	{
		// 获取到包
		return modem_t(msg.at(0));
	}
	else
	{
		// 超时
		return TIMEOUT;
	}

}

Xymodem::modem_t Xymodem::post(ComMessage& msg, QByteArray body) const
{
	msg.set(body);
	m_com->PutPacket(msg);
	return get(msg);
}

void Xymodem::put(ComMessage& msg, QByteArray body) const
{
	msg.set(body);
	m_com->PutPacket(msg);
}

QByteArray Xymodem::make_head_packet() const
{
	QByteArray ba;
	// head
	if (m_type == xmodem)
	{
		ba.append(SOH);
	}
	ba.append('\0');
	ba.append('\0' - 1);

	// body
	const QString filename(m_file->filename());
	ba.append(filename);
	ba.append('\0');
	const QString length(QString::number(m_file->size()));
	ba.append(length);

	if (m_type == xmodem)
	{
		ba.append(128 - filename.size() - 1 - length.size(), '\0');
	}
	// tail
	return Crc::crc16(ba);
}

QByteArray Xymodem::make_body_packet(const int block) const
{
	QByteArray ba;
	// head
	if (m_type == xmodem)
	{
		ba.append(SOH);
	}
	ba.append(char(block + 1));
	ba.append(char(0xFF) - char(block + 1));

	// body
	const QByteArray content = m_file->get_block(block);
	ba.append(content);

	// tail
	return Crc::crc16(ba);
}

QByteArray Xymodem::make_eot_packet() const
{
	return QByteArray(1, EOT);
}

QByteArray Xymodem::make_cancel_packet() const
{
	return QByteArray(2, CAN);
}

QByteArray Xymodem::make_null_paclet() const
{
	QByteArray ba;
	// head
	if (m_type == xmodem)
	{
		ba.append(SOH);
	}
	ba.append('\0');
	ba.append('\0' - 1);

	if (m_type == xmodem)
	{
		ba.append(128, '\0');
	}
	// tail
	return Crc::crc16(ba);
}

int Xymodem::stop() const
{
	if (m_com != nullptr)
	{
		m_com->Disconnect();
	}
	return true;
}
